﻿/*
Copyright © 2011 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.Collections.Generic;
using System.Linq;

namespace Simple.Storage.Api
{
    /// <summary>
    /// Manages the index values associated with a persisted class.
    /// </summary>
    internal class IndexManager
    {
        /// <summary>
        /// The associated data source.
        /// </summary>
        private readonly DataSource datasource;

        /// <summary>
        /// The associated persisted class.
        /// </summary>
        private readonly Type classType;

        /// <summary>
        /// The existing instance descriptors provided by key.
        /// </summary>
        private readonly IDictionary<object, IndexManagerNode> descriptors;

        /// <summary>
        /// Initializes a new instance of the IndexManager class.
        /// </summary>
        /// <param name="datasource">
        /// The associated data source.
        /// </param>
        /// <param name="classType">
        /// The associated persisted class.
        /// </param>
        public IndexManager(DataSource datasource, Type classType)
        {
            if (datasource == null)
            {
                throw new ArgumentNullException("datasource");
            }
            else if (classType == null)
            {
                throw new ArgumentNullException("classType");
            }

            this.datasource = datasource;
            this.classType = classType;
            this.descriptors = new Dictionary<object, IndexManagerNode>();
        }

        /// <summary>
        /// Gets the associated persisted class.
        /// </summary>
        public Type ClassType
        {
            get { return this.classType; }
        }

        /// <summary>
        /// Gets the indexes managed by the current instance - by name.
        /// </summary>
        public IDictionary<string, IIndexDefinition> Indexes
        {
            get { return this.datasource.Indexes[this.classType]; }
        }

        /// <summary>
        /// Gets the persisted class definition associated with the current instance.
        /// </summary>
        public IClassDefinition Definition
        {
            get { return this.datasource.Classes[this.classType]; }
        }

        /// <summary>
        /// Gets the nodes that composes the manager.
        /// </summary>
        /// <remarks>
        /// This dictionary allows to change the internal state of the manager.
        /// That's why this property is internal and used only for serialization purposes.
        /// </remarks>
        /// <seealso cref="IndexManagerSerializer"/>
        internal IDictionary<object, IndexManagerNode> Nodes
        {
            get { return this.descriptors; }
        }

        /// <summary>
        /// Provides a query based on the associated descriptors.
        /// </summary>
        /// <typeparam name="T">The associated persisted class.</typeparam>
        /// <returns>A query on the associated descriptors.</returns>
        public IEnumerable<IDescriptor<T>> ToQuery<T>()
        {
            if (typeof(T) != this.ClassType)
            {
                // TODO: Error message
                throw new NotSupportedException();
            }

            return this.descriptors.Values
                .Select(d => new GenericDescriptor<T>(d, () => this.datasource.Load<T>(d.Key)))
                .Cast<IDescriptor<T>>();
        }

        /// <summary>
        /// Deletes the indexes of a specified key.
        /// </summary>
        /// <param name="key">The reference key.</param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="key"/> parameter is <c>null</c>.
        /// </exception>
        public void OnDelete(object key)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            this.descriptors.Remove(key);
        }

        /// <summary>
        /// Saves the indexed values of specified persisted instance.
        /// </summary>
        /// <param name="value">The persisted instance.</param>
        public void OnSave(object value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            else if (!this.classType.IsInstanceOfType(value))
            {
                // TODO: Error message
                throw new NotSupportedException();
            }

            object key = this.Definition.RetrieveKey(value);
            if (!this.descriptors.ContainsKey(key))
            {
                // create the descriptor of a new instance
                this.descriptors.Add(key, new IndexManagerNode(key));
            }

            foreach (IIndexDefinition index in this.Indexes.Values)
            {
                this.descriptors[key].UpdateIndex(index.Name, index.RetrieveIndexValue(value));
            }
        }
    }
}
